package org.meetunexpectedly.schedul;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.meetunexpectedly.constant.AuditStatus;
import org.meetunexpectedly.constant.RedisConstant;
import org.meetunexpectedly.entity.setting.Setting;
import org.meetunexpectedly.entity.video.Video;
import org.meetunexpectedly.entity.vo.HotVideo;
import org.meetunexpectedly.service.setting.SettingService;
import org.meetunexpectedly.service.video.VideoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description:
 * @Author: pupil
 * @Date: 2024/09/10 下午 5:42
 */
@Component
public class HotRank {

    @Autowired
    private VideoService videoService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private SettingService settingService;

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();

    {
        jackson2JsonRedisSerializer.setObjectMapper(om);
    }

    // 衰变常数
    private static double k = 0.0142;


    /**
     * 热度排行榜
     * 1个小时更新一次
     */
//    @Scheduled(cron = "0 0 */1 * * ?")
//    @Scheduled(cron = "*/1 * * * * ?")
    @Scheduled(cron = "0 0/5 * * * ?")
    public void hotRank() {
        // 控制热门视频数量
        // 队头是热度最低的视频
        TopK topK = new TopK(10, new PriorityQueue<HotVideo>(10, Comparator.comparing(HotVideo::getHot)));

        int limit = 1000;
        // 每次拿1000个
        long id = 1;
        List<Video> videos = videoService.list(new LambdaQueryWrapper<Video>()
                .select(Video::getId, Video::getShareCount, Video::getHistoryCount, Video::getThumbCount, Video::getFavoritesCount,
                        Video::getGmtCreated, Video::getTitle).gt(Video::getId, id)
                .eq(Video::getAuditStatus, AuditStatus.SUCCESS.name).eq(Video::getOpen, 0).last("limit " + limit));

        while (!ObjectUtils.isEmpty(videos)) {
            for (Video video : videos) {
                BigDecimal shareCount = BigDecimal.valueOf(video.getShareCount());
                BigDecimal historyCount = BigDecimal.valueOf(video.getHistoryCount() * 0.8);
                BigDecimal thumbCount = BigDecimal.valueOf(video.getThumbCount());
                BigDecimal favoritesCount = BigDecimal.valueOf(video.getFavoritesCount() * 1.5);

                Date date = new Date();
                long t = date.getTime() - video.getGmtCreated().getTime();
                // 随机获取6位数，用于去重
                BigDecimal w = BigDecimal.valueOf(weightRandom());
                double hot = hot(shareCount.add(historyCount).add(thumbCount).add(favoritesCount).add(w).doubleValue(), TimeUnit.MILLISECONDS.toDays(t));
                HotVideo hotVideo = new HotVideo(hot, video.getId(), video.getTitle());
                topK.add(hotVideo);
            }

            id = videos.get(videos.size() - 1).getId();
            // 获取下一组视频数据，进行循环遍历所有的视频
            videos = videoService.list(new LambdaQueryWrapper<Video>().gt(Video::getId, id).last("limit " + limit));
        }

        // 存储到redis中
        byte[] key = RedisConstant.HOT_RANK.getBytes();
        List<HotVideo> hotVideos = topK.get();
        Double minHot = hotVideos.get(hotVideos.size() - 1).getHot();
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {

            for (HotVideo hotVideo : hotVideos) {
                Double hot = hotVideo.getHot();
                try {
                    hotVideo.setHot(null);
                    // 不这样写铁报错！序列化问题
                    connection.zAdd(key, hot, jackson2JsonRedisSerializer.serialize(om.writeValueAsString(hotVideo)));
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }
            return null;
        });
        if(hotVideos.size() >= 10){
            // 从热度排行榜缓存中删除小于minHot热度的视频
            redisTemplate.opsForZSet().removeRangeByScore(RedisConstant.HOT_RANK, Double.NEGATIVE_INFINITY, minHot);
        }
    }


    /**
     * 热门视频
     * 3个小时更新一次
     */
//    @Scheduled(cron = "0 0 */3 * * ?")
//    @Scheduled(cron = "*/1 * * * * ?")
    @Scheduled(cron = "0 0/5 * * * ?")
    public void hotVideo() {
        // 分页查询3天内的视频(根据当前情况改为90天内)
        int limit = 1000;
        long id = 1;
        List<Video> videos = videoService.selectNDaysAgeVideo(id, 90, limit);
        Double hotLimit = settingService.list(new LambdaQueryWrapper<Setting>()).get(0).getHotLimit();

        // 日历类
        Calendar calendar = Calendar.getInstance();
        // 获取日
        int today = calendar.get(Calendar.DATE);

        while (videos != null && !ObjectUtils.isEmpty(videos)) {
            ArrayList<Long> hotVideos = new ArrayList<>();
            for (Video video : videos) {
                BigDecimal shareCount = BigDecimal.valueOf(video.getShareCount());
                BigDecimal historyCount = BigDecimal.valueOf(video.getHistoryCount() * 0.8);
                BigDecimal thumbCount = BigDecimal.valueOf(video.getThumbCount());
                BigDecimal favoritesCount = BigDecimal.valueOf(video.getFavoritesCount() * 1.5);
                Date date = new Date();
                long t = date.getTime() - video.getGmtCreated().getTime();
                double hot = hot(shareCount.add(historyCount).add(thumbCount).add(favoritesCount).doubleValue(), TimeUnit.MILLISECONDS.toDays(t));

                // 大于X热度说明是热门视频
                if (hot > hotLimit) {
                    hotVideos.add(video.getId());
                }
            }

            if (!ObjectUtils.isEmpty(hotVideos)) {
                String key = RedisConstant.HOT_VIDEO + today;
                redisTemplate.opsForSet().add(key, hotVideos.toArray(new Object[hotVideos.size()]));
                redisTemplate.expire(key, 90, TimeUnit.DAYS);
            }

            // RedisConstant.HOT_VIDEO + 今日日期 作为key  达到元素过期效果
            id = videos.get(videos.size() - 1).getId();
            videos = videoService.selectNDaysAgeVideo(id, 90, limit);
        }


    }

    /**
     * 使用半衰期公式计算热度
     * 半衰期公式: w*e^(-k*t)
     *
     * @param weight 权重（相对于元素核数）
     * @param t      时间
     * @return
     */
    private static double hot(double weight, double t) {
        return weight * Math.exp(-k * t);
    }


    /**
     * 获取随机数
     *
     * @return
     */
    private static double weightRandom() {
        int i = (int) ((Math.random() * 9 + 1) * 100000);
        return i / 1000000.0;
    }
}
